#include <elf.h>

#include "crossbit/ELFMemoryImage.h"
#include "crossbit/SYSCALL.h"

using namespace crossbit;

ELFMemoryImage::ELFMemoryImage(	XTPStr pathname, 
        XTUint32 argc, 
        XTPStr argv[],
        XTPStr envp[] )
: MemoryImage(pathname, argc, argv, envp)
{
    mi_file = SYSCALL::XTOpen(pathname, O_RDONLY); 
    if( -1 == mi_file )
    {
        UTIL::XTLOG("open image error!\n");
        UTIL::XTExit(-1);
    }
}

ELFMemoryImage::~ELFMemoryImage()
{
    SYSCALL::XTClose(mi_file);
}

bool ELFMemoryImage::load()
{
    Elf32_Ehdr file_header;
    XTInt32	ret;
    XTInt32	sizeof_prog_headers;
    Elf32_Phdr *prog_headers;

    ret = SYSCALL::XTRead(mi_file, (void*)(&file_header), sizeof(Elf32_Ehdr));
    if( -1 == ret )
    {
        UTIL::XTLOG("Read ELFMemoryImage Header Error!");
        UTIL::XTExit(-1);
    }

    if( file_header.e_type != ET_EXEC )
    {
        UTIL::XTLOG("Not Executable Image!");
        UTIL::XTExit(-1);
    } 

    mi_entry = file_header.e_entry;
    sizeof_prog_headers = file_header.e_phentsize * file_header.e_phnum;
    prog_headers = new Elf32_Phdr[file_header.e_phnum]; 
    //need to replace with our function

    SYSCALL::XTLseek(mi_file, file_header.e_phoff, SEEK_SET);

    ret = SYSCALL::XTRead(mi_file, (void*)prog_headers, sizeof_prog_headers);
    if( -1 == ret )
    {
        UTIL::XTLOG("Read Prog Header Error!");
        UTIL::XTExit(-1);
    } 
    if( MAP_FAILED == SYSCALL::XTMmap( 0 , GLOBAL::STACK_BASE , PROT_READ | PROT_WRITE , MAP_ANON | MAP_PRIVATE | MAP_FIXED , -1 , 0 ) ){
        UTIL::XTLOG("mmap Error!\n");
        UTIL::XTExit(-1);
    }

    Elf32_Phdr *elf_phdr;
    XTInt32 elf_prot;
    XTInt32 elf_flags;
    XTInt32 page_offset;
    XTInt32 i;

    XTMemAddr highest_addr = 0x0;
    XTUint32  highest_file_size = 0;	
    XTUint32  highest_mem_size = 0;	

    for( i = 0, elf_phdr = (Elf32_Phdr*)(prog_headers); i < file_header.e_phnum; i++, elf_phdr++ )
    {
        if( elf_phdr->p_type != PT_LOAD )
            continue;
        SYSCALL::XTLseek(mi_file, (elf_phdr -> p_offset), SEEK_SET);

        if( -1 == SYSCALL::XTRead(mi_file, (void*)(elf_phdr -> p_vaddr), (elf_phdr -> p_filesz) )){
            UTIL::XTLOG( " read file error\n");
            UTIL::XTExit( -1  );
        }
        page_offset = PAGEOFFSET(elf_phdr->p_vaddr);
        /*
           if( elf_phdr->p_flags & PF_R )
           elf_prot |= PROT_READ;
           if( elf_phdr->p_flags & PF_W )
           elf_prot |= PROT_WRITE;
           if( elf_phdr->p_flags & PF_X )
           elf_prot |= PROT_EXEC;


        //elf_flags = MAP_ANON|MAP_PRIVATE|MAP_DENYWRITE|MAP_EXECUTABLE|MAP_FIXED;
        elf_flags = MAP_PRIVATE | MAP_FIXED;

        page_offset = PAGEOFFSET(elf_phdr->p_vaddr);

        if( MAP_FAILED == SYSCALL::XTMmap((void *)PAGESTART(elf_phdr->p_vaddr), elf_phdr->p_filesz + page_offset, elf_prot, elf_flags, mi_file, elf_phdr->p_offset - page_offset) )
        {
        UTIL::XTLOG("mmap error!\n");
        UTIL::XTExit(-1);
        }
         */

#if 0
        XTUint8 *tmp_buf = new XTUint8[elf_phdr->p_filesz+1];
        //need TODO
        SYSCALL::XTLseek(mi_file, elf_phdr->p_offset, SEEK_SET);
        SYSCALL::XTRead(mi_file, tmp_buf, elf_phdr->p_filesz);
        UTIL::XTMemcpy((void *)elf_phdr->p_vaddr, tmp_buf, elf_phdr->p_filesz);
#endif

        if( elf_phdr->p_vaddr > highest_addr )
        {
            highest_addr = PAGESTART(elf_phdr->p_vaddr);
            highest_file_size = elf_phdr->p_filesz + page_offset;
            highest_mem_size = elf_phdr->p_memsz + page_offset;
        }
    }
    delete[] prog_headers;
    /*
    //Special deal with bss segment.
    elf_prot = PROT_READ | PROT_WRITE;
    elf_flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;

    if( MAP_FAILED == SYSCALL::XTMmap((void *)PAGEALIGN(highest_addr + highest_file_size), (highest_mem_size - highest_file_size), elf_prot, elf_flags, 0, 0) )
    {
    UTIL::XTLOG("mmap error!\n");
    UTIL::XTExit(-1);
    }
    UTIL::XTMemset((void *)(highest_addr + highest_file_size), 0, (highest_mem_size-highest_file_size));
     */
    //				mi_brk = PAGEALIGN(highest_addr + highest_file_size) + PAGEALIGN(highest_mem_size - highest_file_size);
    mi_brk = PAGEALIGN(highest_addr + highest_mem_size);
    SYSCALL::sys_brk = mi_brk;
    SYSCALL::original_brk = mi_brk;
    //SYSCALL::sys_brk = 0x10017000;
    /*				
    //map memory for heap & stack
    elf_prot = PROT_READ | PROT_WRITE;
    elf_flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;
    if( MAP_FAILED == SYSCALL::XTMmap((void *)PAGESTART(mi_brk), GLOBAL::STACK_BASE - mi_brk, elf_prot, elf_flags, 0, 0) )
    {
    UTIL::XTLOG("mmap error!\n");
    UTIL::XTExit(-1);
    }
     */
    mi_stack = setupStack(GLOBAL::STACK_BASE);

    return true;
}


XTMemAddr ELFMemoryImage::map(XTMemAddr src)
{
#if 0
    //return (MemAddr)(mapOffset_+src);
    MemAddr ret;
    if( src == 0 )
        ret = s_map_offset;
    else
        ret = mapOffset_+src;
    return ret;
#endif
}


XTMemAddr ELFMemoryImage::setupStack( XTMemAddr base )
{
    XTUint32 i;
    XTMemAddr sp = base - MAX_ENVIRON;

    XTMemAddr p = sp;

    *(XTInt32*)p = mi_argc;
    p += sizeof(XTInt32);

    XTMemAddr argv_addr = p;
    p += (mi_argc+1) * sizeof(XTPStr);

    XTMemAddr envp_addr = p;
    for( i = 0; mi_envp[i]; ++ i )
        p += sizeof(XTPStr);
    p += sizeof(XTPStr); // for NULL in the end of envp

#define NEW_AUX_ENT(id, val) \
    do { *(int *)p = (id); p += 4 ; *(int *)p = (val); p += 4; } while (0)

    NEW_AUX_ENT(16, 0);
    NEW_AUX_ENT(6, PAGESIZE);
    NEW_AUX_ENT(0x11, 0x64);
    NEW_AUX_ENT(0x3,  0x40034);
    NEW_AUX_ENT(0x4,  0x20);
    NEW_AUX_ENT(0x5,  0x4);
    NEW_AUX_ENT(0x7,  0x0);
    NEW_AUX_ENT(0x8,  0x0);
    NEW_AUX_ENT(0x9,  0x4001a0);
    NEW_AUX_ENT(0xb,  0x0);
    NEW_AUX_ENT(0xc,  0x0);
    NEW_AUX_ENT(0xd,  0x0);
    NEW_AUX_ENT(0xe,  0x0);
    NEW_AUX_ENT(0x0,  0x0);
    NEW_AUX_ENT(0x0,  0x0);
    NEW_AUX_ENT(0x0,  0x0);
    *(XTInt32*)p = 0;
    p += 4;
    *(XTInt8*) p = 0;
    p ++;
    for( i = 0; i < mi_argc; i ++ )
    {
        UTIL::XTStrcpy((XTPStr)p, mi_argv[i]);
        ((XTPStr*)argv_addr)[i] = (XTPStr)(p);
        p += UTIL::XTStrlen(mi_argv[i]) +1;
    }
    ((XTPStr*)argv_addr)[i] = NULL;

    for( i = 0; mi_envp[i]; ++i)
    {
        UTIL::XTStrcpy((XTPStr)p, mi_envp[i]);
        ((XTPStr*)envp_addr)[i] = (XTPStr)(p);
        p += UTIL::XTStrlen(mi_envp[i]) + 1;
    }
    ((XTPStr*)envp_addr)[i] = NULL;

    return (XTMemAddr)(sp);
}

